package cn.com.chec.en.util;

/**
 *	加解密类：使用Blowfish算法
 *	@描述：使用Blowfish的ECB/NoPadding加密模式，通过指定密钥，将输入的20位10进制明文字符串加密成20位10进制密文字符串，
 *	       和将20位10进制密文字符串解密成20位明文字符串。
 *	@作者：李志
 *	@日期：2006.8.15
 */

//import java.security.*;
import java.math.BigInteger;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.apache.log4j.Logger;

public class BlowFish {
	public BlowFish(String key) {
		this.key = key;
	}

	private String key;

	public String getKey() {
		return key;
	}

	public void setKey(String key) {
		this.key = key;
	}

	static Logger log = Logger.getLogger("Blowfish");
	// 指定加密算法及加密模式
	private static final String Algorithm = "Blowfish";
	// 因为是对称加密，且要求密文和明文长度一致，所以加密模式选ECB，并且NoPadding
	private static final String Transformation = "Blowfish/ECB/NoPadding";

	// 定义16进制字符串符号
	private static String HexCode[] = { "0", "1", "2", "3", "4", "5", "6", "7",
			"8", "9", "A", "B", "C", "D", "E", "F" };

	// 将单个字节转换成16进制字符串
	public String byteToHexString(byte b) {
		int n = b;
		if (n < 0)
			n = 256 + n;
		int d1 = n / 16;
		int d2 = n % 16;
		return HexCode[d1] + HexCode[d2];
	}

	// 将字节数组转换成16进制字符串
	public String byteArrayToHexString(byte b[]) {
		String result = "";
		for (int i = 0; i < b.length; i++)
			result = result + byteToHexString(b[i]);
		return result;
	}

	// 将16进制字符串转换成字节数组
	public byte[] hexStr2ByteArr(String strIn) {
		byte[] arrB = strIn.getBytes();
		int iLen = arrB.length;

		// 两个字符表示一个字节，所以字节数组长度是字符串长度除以2
		byte[] arrOut = new byte[iLen / 2];
		for (int i = 0; i < iLen; i = i + 2) {
			String strTmp = new String(arrB, i, 2);
			arrOut[i / 2] = (byte) Integer.parseInt(strTmp, 16);
		}
		return arrOut;
	}

	/********************************************************************************************************************
	 * //加密：根据输入的20位10进制明文和长度为8的倍数的16位进制密钥，加密成20位10进制密文
	 * //szDecPlain为20位10进制字符串，szHexKey为长度是8的倍数的16进制字符串，取值范围从32到448。 //
	 ********************************************************************************************************************/
	public String encryptString(String szDecPlain) {
		try {
			// 将szHexKey转成Byte数组，作为密钥
			byte[] keyBytes = hexStr2ByteArr(key);

			// ---------------------------------------------------------------------------------------------------------
			// 注：本来Java中Long是64位整数，但由于Java没有无符号类型，
			// 故Long只能表示16进制的-8000000000000000（15个0）到7FFFFFFFFFFFFFF（15个F），即-2的63次方到2的63次方减一
			// 根本无法覆盖到FFFFFFFFFFFFFFF(16个F),不满足要求
			// 幸好，Java中有个数学类，BigInteger，不限定位数，可以满足我们的要求！
			// ---------------------------------------------------------------------------------------------------------

			// 将szDecPlain从10进制字符串转成能够被加密的Byte数组plainBytes，需要进行以下4个步骤：
			// 1.将szDecPlain转成BigInteger
			BigInteger plainBigInt = new BigInteger(szDecPlain, 10);
			// 2.将BigInteger转成大写的16进制字符串
			String szHexPlain = plainBigInt.toString(16).toUpperCase();
			// 3.保证szHexPlain为16位，不足前面补“0”
			int iLength = 16 - szHexPlain.length();
			if (iLength > 0) {
				for (int i = 0; i < iLength; i++)
					szHexPlain = "0" + szHexPlain;
			}
			;
			// 4.将szHexPlain字符串转成Byte数组，即可被加密
			byte[] plainBytes = hexStr2ByteArr(szHexPlain);

			// 生成密钥
			SecretKey secretKey = new SecretKeySpec(keyBytes, Algorithm);
			// 按照指定加密模式进行加密
			Cipher c1 = Cipher.getInstance(Transformation);
			// 传递密钥并初始化Cipher
			c1.init(Cipher.ENCRYPT_MODE, secretKey);
			// 对指定的字节数组进行加密
			byte[] cryptoBytes = c1.doFinal(plainBytes);

			// 将cryptoBytes从Byte数组转换成20位10进制字符串szDecCrypto，需要进行以下4个步骤：
			// 1.将密文cryptoBytes转换得到大写的16进制字符串szHexCrypto
			String szHexCrypto = byteArrayToHexString(cryptoBytes);
			// 2.将szHexCrypto字符串转换成BigInteger
			BigInteger cryptoBigInt = new BigInteger(szHexCrypto, 16);
			// 3.将BigInteger转成10进制字符串
			String szDecCrypto = cryptoBigInt.toString(10);
			// 4.保证szDecCrypto为20位，不足不足前面补“0”，即得到最终密文
			int iLength1 = 20 - szDecCrypto.length();
			if (iLength1 > 0) {
				for (int i = 0; i < iLength1; i++)
					szDecCrypto = "0" + szDecCrypto;
			}
			;

			return szDecCrypto;
		} catch (java.security.NoSuchAlgorithmException e1) {
			e1.printStackTrace();
		} catch (javax.crypto.NoSuchPaddingException e2) {
			e2.printStackTrace();
		} catch (java.lang.Exception e3) {
			e3.printStackTrace();
		}
		return null;
	}

	/********************************************************************************************************************
	 * //解密：根据输入的20位10进制密文和长度为8的倍数的16位进制密钥，解密成20位10进制明文
	 * //szDecCrypto为20位10进制字符串，szHexKey为长度是8的倍数的16进制字符串，取值范围从32到448。 //
	 ********************************************************************************************************************/
	public String decryptString(String szDecCrypto) {
		try {
			// 将szHexKey转成Byte数组，作为密钥
			byte[] keyBytes = hexStr2ByteArr(key);

			// ---------------------------------------------------------------------------------------------------------
			// 注：本来Java中Long是64位整数，但由于Java没有无符号类型，
			// 故Long只能表示16进制的-8000000000000000（15个0）到7FFFFFFFFFFFFFF（15个F），即-2的63次方到2的63次方减一
			// 根本无法覆盖到FFFFFFFFFFFFFFF(16个F),不满足要求
			// 幸好，Java中有个数学类，BigInteger，不限定位数，可以满足我们的要求！
			// ---------------------------------------------------------------------------------------------------------

			// 将szDecCrypto从10进制字符串转成能够被解密的Byte数组cryptoBytes，需要进行以下4个步骤：
			// 1.将szDecCrypto转成BigInteger
			BigInteger cryptoBigInt = new BigInteger(szDecCrypto, 10);
			// 2.将BigInteger转成大写的16进制字符串
			String szHexCrypto = cryptoBigInt.toString(16).toUpperCase();
			// 3.保证szHexCrypto为16位，不足前面补“0”
			int iLength = 16 - szHexCrypto.length();
			if (iLength > 0) {
				for (int i = 0; i < iLength; i++)
					szHexCrypto = "0" + szHexCrypto;
			}
			;
			// 4.将szHexCrypto字符串转成Byte数组，即可被加密
			byte[] cryptoBytes = hexStr2ByteArr(szHexCrypto);

			// 生成密钥
			SecretKey secretKey = new SecretKeySpec(keyBytes, Algorithm);
			// 按照指定加密模式进行解密
			Cipher c1 = Cipher.getInstance(Transformation);
			// 传递密钥并初始化Cipher
			c1.init(Cipher.DECRYPT_MODE, secretKey);
			// 对指定的字节数组进行解密
			byte[] plainBytes = c1.doFinal(cryptoBytes);

			// 将plainBytes从Byte数组转换成20位10进制字符串szDecPlain，需要进行以下4个步骤：
			// 1.将明文plainBytes转换得到大写的16进制字符串szHexPlain
			String szHexPlain = byteArrayToHexString(plainBytes);
			// 2.将szHexPlain字符串转换成BigInteger
			BigInteger plainBigInt = new BigInteger(szHexPlain, 16);
			// 3.将BigInteger转成10进制字符串
			String szDecPlain = plainBigInt.toString(10);
			// 4.保证szDecPlain为20位，不足不足前面补“0”，即得到最终明文
			int iLength1 = 20 - szDecPlain.length();
			if (iLength1 > 0) {
				for (int i = 0; i < iLength1; i++)
					szDecPlain = "0" + szDecPlain;
			}
			;

			return szDecPlain;
		} catch (java.security.NoSuchAlgorithmException e1) {
			e1.printStackTrace();
		} catch (javax.crypto.NoSuchPaddingException e2) {
			e2.printStackTrace();
		} catch (java.lang.Exception e3) {
			e3.printStackTrace();
		}
		return null;
	}

	// 测试使用
	public static void main(String[] args) {

		final String szHexKey = "18160728213809530959FFDA";
		final String sDecPlain = "09377038606474503600";

		log.debug("加解密算法测试开始:");
		log.debug("输入的密钥：" + szHexKey);
		log.debug("加密前明文：" + sDecPlain);

		// 申明一个Blowfish对象
		BlowFish oBlowfish = new BlowFish(szHexKey);

		String szDecCrypto = oBlowfish.encryptString(sDecPlain);
		log.debug("加密后密文：" + szDecCrypto);
		System.out.println(szDecCrypto);

		String szDecPlain = oBlowfish.decryptString(szDecCrypto);
		log.debug("解密后明文：" + szDecPlain);

		log.debug("测试成功，结束！");
	}

}
